#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "log.h"

pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
LogDev *log_devs = NULL;
const char *LogLevelName[] = {
#define Func(x) [LOG_##x] = #x,
    LOG_LEVEL_DEF(Func)
#undef Func
};

static void log_insert_dev_head(LogDev *dev) {
    pthread_mutex_lock(&log_mutex);

    if(log_devs)
        dev->prev = log_devs;
    log_devs = dev;

    pthread_mutex_unlock(&log_mutex);
}

static void log_remove_dev(LogDev *dev) {
    pthread_mutex_lock(&log_mutex);

    if(log_devs == NULL)
        goto out;

    if(log_devs == dev) {
        log_devs = dev->prev;
        goto out;
    }

    LogDev *t;
    for(t = log_devs; t->prev; t = t->prev) {
        if(t->prev == dev) {
            t->prev = dev->prev;
            goto out;
        }
    }

out:
    pthread_mutex_unlock(&log_mutex);
    return;
}

LogDev* _log_init_fd(int fd, const char *name, LogLevel level) {
    LogDev *dev = malloc(sizeof(LogDev));
    if(dev == NULL) {
        dprintf(fd, "log_init_fd: malloc %s\n", strerror(errno));
        return NULL;
    }

    if(name == NULL) {
        dprintf(fd, "log_init_fd: need log name\n");
        return NULL;
    }

    dev->prev = NULL;
    dev->name = strdup(name);
    dev->fd = fd;
    dev->level = level;

    log_insert_dev_head(dev);
    return dev;
}

LogDev* _log_init_file(const char *file, const char *name, LogLevel level) {
    int fd = open(file, O_WRONLY|O_CLOEXEC|O_CREAT|O_APPEND, 0644);
    if(fd < 0) {
        fprintf(stderr, "log_init_file: open %s\n", strerror(errno));
        return NULL;
    }
    return _log_init_fd(fd, name, level);
}

void log_free(LogDev *dev) {
    log_remove_dev(dev);

    /* not stdout nor stderr */
    if(dev->fd != 1 && dev->fd != 2)
        close(dev->fd);

    if(dev->name)
        free(dev->name);

    free(dev);
}

void _log_set_level(LogDev *dev, LogLevel level) {
    assert(dev != NULL);
    dev->level = level;
}

LogLevel _log_get_level(LogDev *dev) {
    assert(dev != NULL);
    return dev->level;
}
